﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;

namespace Hids.Classes.AttackSignatures.PacketTests
{
    /// <summary>
    /// Holds the Properties and Methods deisgned to look for a array of bytes with a larger array of bytes
    /// </summary>
    public class Content : PacketTest
    {
        #region Public Properties

        /// <summary>
        /// The byte to look for that signifies an attack
        /// </summary>
        public byte[] BytesToFind;

        /// <summary>
        /// The maximum length to check from the starting point.  If set to 0, check the entire byte area for this identifier
        /// </summary>
        public int MaximumLengthToCheck = 0;

        /// <summary>
        /// Holds the number of bytes to skip from the last packet
        /// </summary>
        public int NumberOfBytesToSkip = 0;

        #endregion

        #region Constructors

        /// <summary>
        /// Creates a Content packet test using the specified parameters
        /// </summary>
        public Content(byte[] bytesToFind, int maximumLengthToCheck, int numberOfBytesToSkip)
        {
            BytesToFind = bytesToFind;
            MaximumLengthToCheck = maximumLengthToCheck;
            NumberOfBytesToSkip = numberOfBytesToSkip;
        }

        public Content(byte[] bytesToFind, int maximumLengthToCheck)
            : this(bytesToFind, maximumLengthToCheck, 0)
        { }

        public Content(byte[] bytesToFind) : this(bytesToFind, 0)
        { }

        /// <summary>
        /// Creates a Content packet test by using a set of Xml Attributes
        /// </summary>
        /// <param name="attributes">A set of Xml Attributes to create the content with.  At a minimum, Bytes To Find must be set.  
        /// The valid attributes and values are:
        /// BytesToFind:  a string of bytes in Hex seperated by a space.  Example:  "BA DD 12"
        /// MaximumLengthToCheck:  integer
        /// NumberOfBytesToSkip:  integer</param>
        public Content(XmlAttributeCollection attributes)
        {
            foreach (XmlAttribute attribute in attributes)
            {
                switch (attribute.Name)
                {
                    case "BytesToFind"://parse the bytes in the hex string
                        List<string> byteParts = new List<string>(attribute.Value.Split(' '));//split the bytes up into each individual byte
                        List<byte> bytesToFind = new List<byte>();
                        foreach (string bytePart in byteParts)//convert the string to an integer and then to a byte and add it to the list.  
                            bytesToFind.Add(Convert.ToByte(Convert.ToInt32(bytePart, 16)));//The convertion is done twice because we don't want the hex value of the string
                        BytesToFind = bytesToFind.ToArray();
                        break;
                    case "MaximumLengthToCheck":
                        MaximumLengthToCheck = int.Parse(attribute.Value);
                        break;
                    case "NumberOfBytesToSkip":
                        NumberOfBytesToSkip = int.Parse(attribute.Value);
                        break;
                    default:
                        throw new ApplicationException("Unknown attribute for content:  " + attribute.Name);
                }
            }

            if (BytesToFind == null)
                throw new ApplicationException("BytesToFind required for a Content test");
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Looks in a byte array for a specific set of bytes starting at a specific location and only checking for so long
        /// </summary>
        /// <param name="payload">The bytes to look for the specific set of bytes in</param>
        /// <param name="lastSearchOffset">The offset to begin looking for the content at</param>
        /// <returns>The index of the first pattern match of content in the bytes.  If the content is not found, -1 is returned</returns>
        public int ContainsBytes(byte[] payload, int lastSearchOffset)
        {
            if (BytesToFind.Length == 0)//if the bytes to find is not set, throw an exception
                throw new ApplicationException("Invalid contentToFind passed to Hids.Classes.NetworkAnalyzer.ContainsByte");
            else if (payload.Length == 0)
                return -1;//if the packet is empty, obviously we can't find the packets

            int lastByteToCheck = payload.Length;
            if (MaximumLengthToCheck != 0 && lastSearchOffset + MaximumLengthToCheck < lastByteToCheck)
                lastByteToCheck = lastSearchOffset + MaximumLengthToCheck;

            for (int i = lastSearchOffset + NumberOfBytesToSkip; i < lastByteToCheck; i++)
            {//search for the bytes within the packet
                bool found = true;
                if (payload[i] == BytesToFind[0])
                {
                    if (payload.Length - BytesToFind.Length < i - 1)//we're too far to match all of the bytes, 
                        return -1; //i.e. content is 5 bytes long and we're 4 bytes from the end

                    for (int k = i + 1; k < lastByteToCheck && k - i < BytesToFind.Length; k++)
                    {
                        if (payload[k] != BytesToFind[k - i])
                        {
                            found = false;
                            break;
                        }
                    }

                    if (found)
                        return i;
                }
            }

            return -1;
        }

        #endregion
    }
}
